﻿using lib.dmt.project.Extensions;
using lib.dmt.teltonika.server.Events;
using lib.dmt.teltonika.server.MQ;
using lib.dmt.teltonika.server.Parsers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Data;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Text;
using ErrorEventArgs = lib.dmt.teltonika.server.Events.ErrorEventArgs;

namespace lib.dmt.teltonika.server.TCP
{
    public class TcpServer : IDisposable
    {
        private TcpListener? _listener;
        private CancellationTokenSource? _cancellationTokenSource;
        private Task? _acceptTask;
        private bool _disposed;
        private readonly object _lock = new object();

        public ConnectionManager? ConnectionManager { get; }
        public int Port { get; private set; }
        public bool IsRunning { get; private set; }

        public event EventHandler<ClientConnectedEventArgs>? ClientConnected;
        public event EventHandler<ClientDisconnectedEventArgs>? ClientDisconnected;
        public event EventHandler<DataByteReceivedEventArgs>? DataReceived;
        public event EventHandler<ErrorEventArgs>? Error;

        private readonly ILoggerFactory _loggerFactory;
        private readonly ILogger _logger;
        private readonly ILogger _jsonReceiveLogger;
        private readonly ILogger _byteReceiveLogger;

        private readonly IConfiguration? _config;
        public TcpServer(ILoggerFactory? loggerFactory, IConfiguration? config)
        {
            this._loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
            this._logger = _loggerFactory.CreateLogger("AppLogger");
            this._jsonReceiveLogger = _loggerFactory.CreateLogger("ReceiveJsonLogger");
            this._byteReceiveLogger = _loggerFactory.CreateLogger("ReceiveByteLogger");

            this._config = config ?? throw new ArgumentNullException(nameof(config));
            ConnectionManager = new ConnectionManager();

            this.mqJsonPublisher = new TeltonikaMessagePublisher(_logger,
                _config.GetValue<string>("RabbitMQJsonMessageToQueue:ConnectionString") ?? string.Empty,
                _config.GetValue<string>("RabbitMQJsonMessageToQueue:ConnectionArgument") ?? string.Empty); 
        }

        private TeltonikaMessagePublisher? mqJsonPublisher;

        public void Start(int port, IPAddress? ipAddress = null)
        {
            if (IsRunning)
            {
                _logger?.LogError($"[TcpServer]; failed to start the service due to the IsRunning={IsRunning}");
                return;
            }

            lock (_lock)
            {
                Port = port;
                ipAddress ??= IPAddress.Any;

                _cancellationTokenSource = new CancellationTokenSource();
                _listener = new TcpListener(ipAddress, port);

                _logger?.LogDebug($"[TcpServer]; started to listen the server; IP={ipAddress.AddressFamily.ToString()}; Port:{Port}");

                try
                {
                    _listener.Start();
                    IsRunning = true;

                    _acceptTask = Task.Factory.StartNew(
                        () => AcceptConnectionsAsync(_cancellationTokenSource.Token),
                        _cancellationTokenSource.Token,
                        TaskCreationOptions.LongRunning,
                        TaskScheduler.Default
                    );

                }
                catch (Exception ex)
                {
                    _logger?.LogCritical(ex, $"[TcpServer]; EXCEPTION; {ex.Message}");
                    IsRunning = false;
                    throw new InvalidOperationException("Failed to start TCP server", ex);
                }
            }
        }

        private async Task AcceptConnectionsAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    var tcpClient = await _listener!.AcceptTcpClientAsync().ConfigureAwait(false);

                    _ = Task.Factory.StartNew(
                        () => HandleNewConnection(tcpClient, cancellationToken),
                        cancellationToken,
                        TaskCreationOptions.DenyChildAttach,
                        TaskScheduler.Default
                    );
                }
                catch (ObjectDisposedException)
                {
                    // Listener was stopped
                    break;
                }
                catch (Exception ex)
                {
                    Error?.Invoke(this, new ErrorEventArgs(null, ex));
                }
            }
        }

        private void HandleNewConnection(TcpClient tcpClient, CancellationToken cancellationToken)
        {
            TcpClientConnection? connection = null;

            try
            {
                // Configure TCP client for high performance
                tcpClient.NoDelay = true; // Disable Nagle's algorithm for low latency
                tcpClient.SendBufferSize = 8192;
                tcpClient.ReceiveBufferSize = 8192;

                connection = new TcpClientConnection(_logger, tcpClient);

                // Subscribe to events
                connection.DataByteReceived += (sender, e) => DataReceived?.Invoke(this, e);
                connection.Error += (sender, e) => Error?.Invoke(this, e);
                connection.Disconnected += (sender, e) =>
                    ClientDisconnected?.Invoke(this, new ClientDisconnectedEventArgs(connection, "Disconnected"));

                connection.DataCode8EReceived += Connection_DataCode8EReceived;
                connection.TeltonikaRawByteReceived += Connection_TeltonikaRawByteReceived;

                // Add to connection manager
                ConnectionManager?.AddConnection(connection);

                // Start receiving data
                connection.StartReceiving();

                // Raise connected event
                ClientConnected?.Invoke(this, new ClientConnectedEventArgs(connection));
            }
            catch (Exception ex)
            {
                Error?.Invoke(this, new ErrorEventArgs(connection, ex));
                connection?.Dispose();
                tcpClient.Close();
            }
        }

        private void Connection_TeltonikaRawByteReceived(object? sender, TeltonikaRawByteReceivedEventArgs e)
        {
            Task.Run(() =>
            {
                _byteReceiveLogger?.LogDebug($"{e.ByteRecord.IMEI}; {BitConverter.ToString(e.ByteRecord.Data)}");
            });
        }

        private void Connection_DataCode8EReceived(object? sender, Code8EReceivedEventArgs e)
        {
            Task.Run(() => 
            {
                _jsonReceiveLogger.LogDebug($"{e.IMEI}; {e.JsonPayload}");
                var avlDataPacket = JsonConvert.DeserializeObject<AVLDataPacket>(e.JsonPayload);
                if (avlDataPacket == null)
                {
                    _logger?.LogCritical($"[TcpServer]; failed to DeserializeObject<AVLDataPacket>, payload={e.JsonPayload}");
                    return;
                }

                for (int i = 0; i < avlDataPacket.AVLData.Count; i++)
                {
                    var avlData = avlDataPacket.AVLData[i];
                    mqJsonPublisher?.Publish(JsonConvert.SerializeObject(avlData));
                    _logger?.LogTrace($"[TcpServer]; PUB_OK; IMEI:{avlData.IMEI}; RecordLocalTime:{avlData.Timestamp.ToLocalTime().ToLocalString()}");
                }
            });
        }

        public void Stop()
        {
            lock (_lock)
            {
                if (!IsRunning) return;

                _cancellationTokenSource?.Cancel();
                _listener?.Stop();

                ConnectionManager?.DisconnectAll("Server stopped");

                IsRunning = false;
            }
        }

        public async Task BroadcastAsync(byte[] data, Func<TcpClientConnection, bool>? filter = null)
        {
            var connections = filter != null
                ? ConnectionManager?.GetConnectionsByFilter(filter)
                : ConnectionManager?.GetAllConnections();

            var sendTasks = connections?
                .Where(c => c.State == EConnectionState.Connected)
                .Select(connection => connection.SendAsync(data).ContinueWith(t =>
                {
                    if (t.IsFaulted)
                    {
                        Error?.Invoke(this, new ErrorEventArgs(connection, t.Exception));
                    }
                }));

            await Task.WhenAll(sendTasks!);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    Stop();
                    _cancellationTokenSource?.Dispose();
                    _listener?.Stop();
                    ConnectionManager?.DisconnectAll();
                }
                _disposed = true;
            }
        }

        ~TcpServer()
        {
            Dispose(false);
        }
    }
}
